/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/* ========================================================================== */
/*                                                                            */
/*   we_indexlist_multiple.cpp                                                */
/*   (c) 2001 Author                                                          */
/*                                                                            */
/*   Description                                                              */
/*                                                                            */
/* ========================================================================== */
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "we_indexlist.h"

using namespace std;

namespace WriteEngine
{
/****************************************************************
 * DESCRIPTION:
 * Public Function for adding a header and more than one row id
 *
 * PARAMETERS:
 *    input
 *        pFile      - File Handler
 *        rid        - Input row ID
 *        key        - Input key value
 *    output
 *        listHdrPtr - Output a pointer to the index list header
 *                     passed back to the caller
 *                     it will containlbid,sbid,entry
 * RETURN:
 *    success    - successfully created the index list header
 *    failure    - it did not create the index list header
 ***********************************************************/

const int IndexList::addIndexListHdr(FILE* pFile, const RID* ridList, const int size, const uint64_t& key,
                                     IdxEmptyListEntry* newEmptyListPtr)
{
  int rc;
  CommBlock cb;
  m_pFile = pFile;
  cb.file.oid = m_oid;
  cb.file.pFile = m_pFile;

  if (size < 1)
    return ERR_IDX_LIST_INVALID_ADDHDR;

  // cout << "key =" << key << endl;
  // Initialize three blokcs
  rc = init();
  // The same as a single rowid
  rc = addIndexListHdr(pFile, ridList[0], key, newEmptyListPtr);

  if (size == 1)
  {
    return rc;
  }

  // More than one row id
  // Set up the header structure
  m_hdrLbid = newEmptyListPtr->fbo;
  m_hdrSbid = newEmptyListPtr->sbid;
  m_hdrEntry = newEmptyListPtr->entry;
  rc = updateIndexList(pFile, ridList[1], key, newEmptyListPtr);

  if (rc != NO_ERROR)
    return rc;

  if (size == 2)
    return rc;

  rc = updateIndexList(pFile, ridList[2], key, newEmptyListPtr);

  if (rc != NO_ERROR)
    return rc;

  if (size == 3)
    return rc;

  // DONE with the header
  // add more row id into the subblock and block
  // first segment is a subblock
  m_curType = LIST_SUBBLOCK_TYPE;
  m_type = LIST_SUBBLOCK_TYPE;
  int startPos = 3;
  rc = getSubBlk();

  if (size > 3)
    rc = addRidList(ridList, size, startPos);

  if (rc != NO_ERROR)
    return rc;

  rc = updateIndexListWrite();
  // rc = getSubBlk();
  // cout << "DEBUG INFO====================================="<< endl;
  // cout<< " m_oid=" << m_oid << endl;
  // cout<< " key= " << key << " header lbid=" << newEmptyListPtr->fbo
  //    << " header sbid= " << newEmptyListPtr->sbid
  //    << " header entry= " << newEmptyListPtr->entry <<endl;
  return rc;
};
/************************************************************
 * Description
 * Private Function, Internal use
 * For adding a whole subblock or block
 * The segment is a clean slate and the lbid has been allocated
 * Adding more rowid into the list, first time
 * without any delete action preceeding this
 * Fill up the subblock 31 entries first
 * Then blocks if needed
 * input -- ridList - rowid List
 * input -- size    - total array size
 *
 ************************************************************/
const int IndexList::addRidList(const RID* ridList, const int size, int& startPos)
{
  int remainder = size - startPos;
  int insCnt, maxCount;
  bool needNextBlk = false;
  int rc = NO_ERROR;
  // int count =2;
  int count = 0;
  CommBlock cb;

  cb.file.oid = m_oid;
  cb.file.pFile = m_pFile;

  IdxEmptyListEntry newEmptyEntry;
  rc = getSubBlk(m_lbid, m_sbid, m_entry);

  if (rc != NO_ERROR)
    return rc;

  m_curType = ((IdxEmptyListEntry*)&(m_curIdxRidListHdr.nextIdxRidListPtr))->type;
  maxCount = MAX_SUB_RID_CNT;
  m_curLevel = 0;
  m_curLevelPos = 0;
  m_curBlkPos = 0;
  rc = readCurBlk();

  // startPos means how many has been inserted
  while (startPos < size)
  {
    rc = getNextInfoFromBlk();
    count = m_lastIdxRidListPtr.count;

    if (m_useNarray)
      m_curLevel = ((IdxRidNextListPtr*)&m_lastIdxRidListPtr)->curLevel;

    if (remainder > (maxCount - count))
    {
      insCnt = maxCount - count;
      remainder = remainder - insCnt;
      needNextBlk = true;
    }
    else
    {
      insCnt = remainder;
      remainder = 0;
      needNextBlk = false;
    }

    // Prepare to write the ridList
    // write the current subblock or block
    for (int i = count; i < (count + insCnt); i++)
    {
      // cout << "startPos->" << startPos << endl;
      RID newRID = ridList[startPos];
      rc = insertRid(newRID, i);
      startPos++;
      m_curIdxRidListHdr.idxRidListSize.size++;
      m_hdrBlock.state = BLK_WRITE;
    }  // end for

    setSubBlockEntry(m_hdrBlock.data, m_hdrSbid, m_hdrEntry, LIST_HDR_SIZE, &m_curIdxRidListHdr);
    m_hdrBlock.state = BLK_WRITE;

    // write out the last pointer and it depends on how much rid left
    if (!needNextBlk)
    {
      // No more, the end. Just update the current block
      // Not read from existing block, so initiate one
      if ((m_useNarray) && (m_curType == LIST_BLOCK_TYPE))
        rc = updateCurCountInArray(insCnt);
      else
      {
        m_lastIdxRidListPtr.count = count + insCnt;
        rc = setNextInfoFromBlk(m_lastIdxRidListPtr);
      }

      if (m_lastLbid != m_lbid)
        rc = setLastLbid(m_lbid);

      if (rc != NO_ERROR)
      {
        return ERR_IDX_LIST_INVALID_ADD_LIST;
      }

      return rc;  // Done
    }
    else  // new link
    {
      // take care the last entry with the new link
      int lastCount = 0;
      m_segType = LIST_BLOCK_TYPE;
      memset(&newEmptyEntry, 0, sizeof(newEmptyEntry));
      rc = getSegment(m_pFile, ENTRY_BLK, &newEmptyEntry);
      // handle current block update before move to the new block
      lastCount = count + insCnt;

      if ((m_curType == LIST_SUBBLOCK_TYPE) || (!m_useNarray))
      {
        m_lastIdxRidListPtr.llp = ((IdxRidListHdrPtr*)&newEmptyEntry)->llp;
        m_lastIdxRidListPtr.type = LIST_BLOCK_TYPE;
        m_lastIdxRidListPtr.spare = 0x0;
        m_lastIdxRidListPtr.count = lastCount;
      }

      m_nextLbid = ((IdxEmptyListEntry*)&newEmptyEntry)->fbo;
      m_nextSbid = 0;
      m_nextEntry = 0;
      m_nextType = m_segType;

      if ((m_curType == LIST_SUBBLOCK_TYPE) || (!m_useNarray))
      {
        // when current block is a subblock or single child link
        rc = setNextInfoFromBlk(m_lastIdxRidListPtr);
      }
      else
      {
        rc = updateLastPtrAndParent(lastCount);
      }

      // Move on to the new block for insertions
      m_lbid = newEmptyEntry.fbo;
      m_sbid = 0;
      m_entry = 0;
      m_curType = m_nextType;

      rc = readCurBlk();
      // make sure no garbage
      IdxRidListPtr idxRidListPtr;
      memset(&idxRidListPtr, 0, sizeof(idxRidListPtr));
      rc = setNextInfoFromBlk(idxRidListPtr);

      if (m_useNarray)
      {
        maxCount = MAX_BLK_NARRAY_RID_CNT;
        rc = initCurBlock();
      }
      else
        maxCount = MAX_BLK_RID_CNT;

      count = 0;
    }  // end else if needs next block
  }    // end while

  return rc;
}
/****************************************************************
 * DESCRIPTION:
 * (0) THIS FUNCTION CAN ONLY BE CALLED WITH THE PUBLIC
 *
 * (1) Given a key value and a row ID, update the link list
 * Converted
 * PARAMETERS:
 *    Input rid
 *        --row ID
 *    Input key
 *        -- key value
 *
 * RETURN:
 *    success    - successfully created the index list header
 *    failure    - it did not create the index list header
 ***********************************************************/
const int IndexList::updateIndexList(FILE* pFile, const RID* ridList, const int size, const uint64_t& key,
                                     IdxEmptyListEntry* curIdxRidListHdrPtr)
{
  int rc;
  CommBlock cb;
  m_pFile = pFile;
  cb.file.oid = m_oid;
  cb.file.pFile = m_pFile;

  m_pFile = pFile;
  // cout << "key=" << key << endl;
  rc = init();
  rc = updateIndexList(pFile, ridList[0], key, curIdxRidListHdrPtr);

  if (size == 1)
    return rc;

  rc = updateIndexList(pFile, ridList[1], key, curIdxRidListHdrPtr);

  if (size == 2)
    return rc;

  rc = getHdrInfo(curIdxRidListHdrPtr);

  if (rc != NO_ERROR)
  {
    return rc;
  }

  if (key != m_curIdxRidListHdr.key)
  {
    // cout << "line 829->Error Key ->" << key << endl;
    // cout << "m_curIdxRidListHdr.key->"<<m_curIdxRidListHdr.key<< endl;
    // cout << "m_hdrLbid->" << m_hdrLbid << endl;
    // cout << "m_hdrSbid->" << m_hdrSbid << endl;
    // cout << "m_hdrEntry->" << m_hdrEntry << endl;
    // cout << "m_oid->" << m_oid << endl;
    return ERR_IDX_LIST_INVALID_KEY;
  }

  int startPos = 2;
  rc = updateIndexList(ridList, size, startPos);

  if (rc != NO_ERROR)
  {
    return rc;
  }

  rc = updateIndexListWrite();

  if (rc != NO_ERROR)
  {
    return rc;
  }

  return rc;
}

/****************************************************************
 * DESCRIPTION:
 * (0) THIS FUNCTION CAN ONLY BE CALLED WITH THE PUBLIC
 *
 * (1) Given a key value and a row ID, update the link list
 * Converted
 * PARAMETERS:
 *    Input rid
 *        --row ID
 *    Input key
 *        -- key value
 *
 * RETURN:
 *    success    - successfully created the index list header
 *    failure    - it did not create the index list header
 ***********************************************************/
const int IndexList::updateIndexList(const RID* ridList, const int size, int& startPos)
{
  IdxEmptyListEntry newEmptyEntry;
  int rc = NO_ERROR, width = 8;
  int pos = 0, totalbytes = 0, maxCount = 0;
  int remainder = size - startPos;
  int count = 0;
  bool needNextBlk = false;

  IdxRidListEntry idxRidListEntry;
  int oldType;

  CommBlock cb;
  cb.file.oid = m_oid;
  cb.file.pFile = m_pFile;

  if (m_pFile == NULL)
    return ERR_IDX_LIST_INVALID_UPDATE;

  // Header should be available by now
  if (((long long)m_hdrLbid == -1LL) || (m_hdrSbid == -1) || (m_hdrEntry == -1))
    return ERR_IDX_LIST_INVALID_UPDATE;

  width = LIST_ENTRY_WIDTH;
  memset(&m_lastIdxRidListPtr, 0, sizeof(m_lastIdxRidListPtr));

  if (m_lastLbid == (uint64_t)INVALID_LBID)
  {
    rc = getSubBlk(m_lbid, m_sbid, m_entry);
    m_curType = ((IdxEmptyListEntry*)&(m_curIdxRidListHdr.nextIdxRidListPtr))->type;
    // First link
    pos = LIST_SUB_LLP_POS;
    totalbytes = SUBBLOCK_TOTAL_BYTES;
    maxCount = MAX_SUB_RID_CNT;
    m_segType = LIST_SUBBLOCK_TYPE;  // This is for next segment type
    m_curType = LIST_SUBBLOCK_TYPE;
  }
  else
  {
    m_lbid = m_lastLbid;
    m_sbid = 0;
    m_entry = 0;
    pos = LIST_BLOCK_LLP_POS;
    totalbytes = BYTE_PER_BLOCK;

    if (m_useNarray)
      maxCount = MAX_BLK_NARRAY_RID_CNT;
    else
      maxCount = MAX_BLK_RID_CNT;

    m_curType = LIST_BLOCK_TYPE;
    m_segType = LIST_BLOCK_TYPE;
  }

  while (startPos < size)
  {
    rc = getNextInfoFromBlk();
    count = m_lastIdxRidListPtr.count;

    if (m_useNarray)
      m_curLevel = ((IdxRidNextListPtr*)&m_lastIdxRidListPtr)->curLevel;

    int availCount = maxCount - count;
    int insCnt = 0;
    m_nextType = m_lastIdxRidListPtr.type;

    if (availCount > 0)
    {
      if (remainder > availCount)
      {
        insCnt = availCount;
        needNextBlk = true;
      }
      else
      {
        insCnt = remainder;
        needNextBlk = false;
      }

      for (int i = 0; i < insCnt; i++)
      {
        idxRidListEntry.type = LIST_RID_TYPE;
        idxRidListEntry.spare = 0x0;
        idxRidListEntry.rid = ridList[startPos];
        startPos++;
        remainder--;
        m_lastIdxRidListPtr.count++;
        availCount--;

        if (m_lbid != m_hdrLbid)
        {
          if ((m_curBlock.state != BLK_INIT) && (m_curBlock.lbid == m_lbid))
            setSubBlockEntry(m_curBlock.data, m_sbid, i + count, width, &idxRidListEntry);
          else
          {
            // return ERR_IDX_LIST_INVALID_UPDATE;
            rc = readCurBlk();

            if (rc != NO_ERROR)
              return ERR_IDX_LIST_INVALID_BLK_READ;

            setSubBlockEntry(m_curBlock.data, m_sbid, i + count, width, &idxRidListEntry);
          }

          m_curBlock.dirty = true;
          m_curBlock.state = BLK_WRITE;
        }
        else
        {
          setSubBlockEntry(m_hdrBlock.data, m_sbid, i + count, width, &idxRidListEntry);
          m_hdrBlock.dirty = true;
          m_hdrBlock.state = BLK_WRITE;
        }

        m_curIdxRidListHdr.idxRidListSize.size++;
        m_hdrBlock.state = BLK_WRITE;
      }  // end for

      setSubBlockEntry(m_hdrBlock.data, m_hdrSbid, m_hdrEntry, LIST_HDR_SIZE, &m_curIdxRidListHdr);
      m_hdrBlock.state = BLK_WRITE;
    }  // endif availCount>0
    else if (remainder > 0)
    {
      needNextBlk = true;
    }
    else if (remainder == 0)
    {
      needNextBlk = false;
    }

    if (!needNextBlk)
    {
      // No more, the end. Just update the current block
      if (insCnt > 0)
      {
        m_lastIdxRidListPtr.count = count + insCnt;

        if (m_lbid != m_hdrLbid)
        {
          if ((m_curBlock.lbid == m_lbid) && (m_curBlock.state != BLK_INIT))
            setSubBlockEntry(m_curBlock.data, m_sbid, pos, width, &m_lastIdxRidListPtr);
          else
            return ERR_IDX_LIST_WRONG_LBID_WRITE;

          m_curBlock.state = BLK_WRITE;
        }
        else
        {
          setSubBlockEntry(m_hdrBlock.data, m_sbid, pos, width, &m_lastIdxRidListPtr);
          m_hdrBlock.state = BLK_WRITE;
        }

        if (m_curType == LIST_SUBBLOCK_TYPE)
        {
          if (m_lastLbid != INVALID_LBID)
          {
            uint64_t zlbid = INVALID_LBID;
            rc = setLastLbid(zlbid);
          }
        }
        else
        {
          if (m_lastLbid != m_lbid)
            rc = setLastLbid(m_lbid);
        }

        if (rc != NO_ERROR)
        {
          return ERR_IDX_LIST_INVALID_UPDATE;
        }

        return rc;  // Done
      }
      else
        return NO_ERROR;
    }  // no new link
    else
    {
      // take care the last entry with the new link
      int lastCount = 0;
      lastCount = count + insCnt;
      m_lastIdxRidListPtr.type = LIST_BLOCK_TYPE;
      m_lastIdxRidListPtr.spare = 0x0;
      m_lastIdxRidListPtr.count = lastCount;

      if (m_nextType != LIST_BLOCK_TYPE)
      {
        m_segType = LIST_BLOCK_TYPE;
        rc = getSegment(m_pFile, ENTRY_BLK, &newEmptyEntry);

        if (rc != NO_ERROR)
        {
          cout << "Indexlist->Free mgr getSegment ERROR CODE rc=" << rc << endl;
          return rc;
        }

        // handle current block update before move to the new
        m_lastIdxRidListPtr.llp = ((IdxRidListHdrPtr*)&newEmptyEntry)->llp;
        // For Narray
        m_nextLbid = ((IdxEmptyListEntry*)&newEmptyEntry)->fbo;
        m_nextSbid = 0;
        m_nextEntry = 0;
        oldType = m_nextType;
        m_nextType = m_segType;
      }
      else
      {
        m_nextLbid = ((IdxEmptyListEntry*)&m_lastIdxRidListPtr)->fbo;
        m_nextSbid = 0;
        m_nextEntry = 0;
        oldType = m_nextType;
        m_nextType = m_segType;
      }

      if (m_curType == LIST_SUBBLOCK_TYPE)
      {
        // when current block is a subblock
        if (m_lbid == m_hdrLbid)
        {
          // header should be read already
          setSubBlockEntry(m_hdrBlock.data, m_sbid, pos, width, &m_lastIdxRidListPtr);
          m_hdrBlock.state = BLK_WRITE;
        }
        else
        {
          rc = readCurBlk();
          setSubBlockEntry(m_curBlock.data, m_sbid, pos, width, &m_lastIdxRidListPtr);
          m_curBlock.state = BLK_WRITE;
        }
      }
      else
      {
        if (m_useNarray)
          rc = updateLastPtrAndParent(lastCount);
        else
          rc = updateLastPtr(lastCount);
      }

      writeCurBlk();
      m_lbid = m_nextLbid;
      m_sbid = 0;
      m_entry = 0;
      // m_lastLbid = m_lbid;
      m_curType = m_nextType;
      pos = LIST_BLOCK_LLP_POS;
      totalbytes = BYTE_PER_BLOCK;
      rc = readCurBlk();

      if (m_useNarray)
      {
        maxCount = MAX_BLK_NARRAY_RID_CNT;

        if (oldType != LIST_BLOCK_TYPE)
          rc = initCurBlock();
      }
      else
        maxCount = MAX_BLK_RID_CNT;

    }  // end else if needs next block

  }  // end while

  return rc;
}
}  // namespace WriteEngine
